/*
 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 2001-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id: Stylesheet.java,v 1.5 2005/09/28 13:48:16 pvedula Exp $
 */

package com.sun.org.apache.xalan.internal.xsltc.compiler;

import java.util.Vector;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Properties;
import java.util.StringTokenizer;

import com.sun.org.apache.xml.internal.utils.SystemIDResolver;
import com.sun.org.apache.bcel.internal.generic.ANEWARRAY;
import com.sun.org.apache.bcel.internal.generic.BasicType;
import com.sun.org.apache.bcel.internal.generic.ConstantPoolGen;
import com.sun.org.apache.bcel.internal.generic.FieldGen;
import com.sun.org.apache.bcel.internal.generic.GETFIELD;
import com.sun.org.apache.bcel.internal.generic.GETSTATIC;
import com.sun.org.apache.bcel.internal.generic.INVOKEINTERFACE;
import com.sun.org.apache.bcel.internal.generic.INVOKESPECIAL;
import com.sun.org.apache.bcel.internal.generic.INVOKEVIRTUAL;
import com.sun.org.apache.bcel.internal.generic.ISTORE;
import com.sun.org.apache.bcel.internal.generic.InstructionHandle;
import com.sun.org.apache.bcel.internal.generic.InstructionList;
import com.sun.org.apache.bcel.internal.generic.LocalVariableGen;
import com.sun.org.apache.bcel.internal.generic.NEW;
import com.sun.org.apache.bcel.internal.generic.NEWARRAY;
import com.sun.org.apache.bcel.internal.generic.PUSH;
import com.sun.org.apache.bcel.internal.generic.PUTFIELD;
import com.sun.org.apache.bcel.internal.generic.PUTSTATIC;
import com.sun.org.apache.bcel.internal.generic.TargetLostException;
import com.sun.org.apache.bcel.internal.util.InstructionFinder;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ClassGenerator;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ErrorMsg;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.MethodGenerator;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.Type;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.TypeCheckError;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.Util;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTM;

/**
 * @author Jacek Ambroziak
 * @author Santiago Pericas-Geertsen
 * @author Morten Jorgensen
 */
public final class Stylesheet extends SyntaxTreeNode {

  /**
   * XSLT version defined in the stylesheet.
   */
  private String _version;

  /**
   * Internal name of this stylesheet used as a key into the symbol table.
   */
  private QName _name;

  /**
   * A URI that represents the system ID for this stylesheet.
   */
  private String _systemId;

  /**
   * A reference to the parent stylesheet or null if topmost.
   */
  private Stylesheet _parentStylesheet;

  /**
   * Contains global variables and parameters defined in the stylesheet.
   */
  private Vector _globals = new Vector();

  /**
   * Used to cache the result returned by <code>hasLocalParams()</code>.
   */
  private Boolean _hasLocalParams = null;

  /**
   * The name of the class being generated.
   */
  private String _className;

  /**
   * Contains all templates defined in this stylesheet
   */
  private final Vector _templates = new Vector();

  /**
   * Used to cache result of <code>getAllValidTemplates()</code>. Only
   * set in top-level stylesheets that include/import other stylesheets.
   */
  private Vector _allValidTemplates = null;

  /**
   * Counter to generate unique mode suffixes.
   */
  private int _nextModeSerial = 1;

  /**
   * Mapping between mode names and Mode instances.
   */
  private final Hashtable _modes = new Hashtable();

  /**
   * A reference to the default Mode object.
   */
  private Mode _defaultMode;

  /**
   * Mapping between extension URIs and their prefixes.
   */
  private final Hashtable _extensions = new Hashtable();

  /**
   * Reference to the stylesheet from which this stylesheet was
   * imported (if any).
   */
  public Stylesheet _importedFrom = null;

  /**
   * Reference to the stylesheet from which this stylesheet was
   * included (if any).
   */
  public Stylesheet _includedFrom = null;

  /**
   * Array of all the stylesheets imported or included from this one.
   */
  private Vector _includedStylesheets = null;

  /**
   * Import precendence for this stylesheet.
   */
  private int _importPrecedence = 1;

  /**
   * Minimum precendence of any descendant stylesheet by inclusion or
   * importation.
   */
  private int _minimumDescendantPrecedence = -1;

  /**
   * Mapping between key names and Key objects (needed by Key/IdPattern).
   */
  private Hashtable _keys = new Hashtable();

  /**
   * A reference to the SourceLoader set by the user (a URIResolver
   * if the JAXP API is being used).
   */
  private SourceLoader _loader = null;

  /**
   * Flag indicating if format-number() is called.
   */
  private boolean _numberFormattingUsed = false;

  /**
   * Flag indicating if this is a simplified stylesheets. A template
   * matching on "/" must be added in this case.
   */
  private boolean _simplified = false;

  /**
   * Flag indicating if multi-document support is needed.
   */
  private boolean _multiDocument = false;

  /**
   * Flag indicating if nodset() is called.
   */
  private boolean _callsNodeset = false;

  /**
   * Flag indicating if id() is called.
   */
  private boolean _hasIdCall = false;

  /**
   * Set to true to enable template inlining optimization.
   *
   * @see XSLTC#_templateInlining
   */
  private boolean _templateInlining = false;

  /**
   * A reference to the last xsl:output object found in the styleshet.
   */
  private Output _lastOutputElement = null;

  /**
   * Output properties for this stylesheet.
   */
  private Properties _outputProperties = null;

  /**
   * Output method for this stylesheet (must be set to one of
   * the constants defined below).
   */
  private int _outputMethod = UNKNOWN_OUTPUT;

  // Output method constants
  public static final int UNKNOWN_OUTPUT = 0;
  public static final int XML_OUTPUT = 1;
  public static final int HTML_OUTPUT = 2;
  public static final int TEXT_OUTPUT = 3;

  /**
   * Return the output method
   */
  public int getOutputMethod() {
    return _outputMethod;
  }

  /**
   * Check and set the output method
   */
  private void checkOutputMethod() {
    if (_lastOutputElement != null) {
      String method = _lastOutputElement.getOutputMethod();
      if (method != null) {
        if (method.equals("xml")) {
          _outputMethod = XML_OUTPUT;
        } else if (method.equals("html")) {
          _outputMethod = HTML_OUTPUT;
        } else if (method.equals("text")) {
          _outputMethod = TEXT_OUTPUT;
        }
      }
    }
  }

  public boolean getTemplateInlining() {
    return _templateInlining;
  }

  public void setTemplateInlining(boolean flag) {
    _templateInlining = flag;
  }

  public boolean isSimplified() {
    return (_simplified);
  }

  public void setSimplified() {
    _simplified = true;
  }

  public void setHasIdCall(boolean flag) {
    _hasIdCall = flag;
  }

  public void setOutputProperty(String key, String value) {
    if (_outputProperties == null) {
      _outputProperties = new Properties();
    }
    _outputProperties.setProperty(key, value);
  }

  public void setOutputProperties(Properties props) {
    _outputProperties = props;
  }

  public Properties getOutputProperties() {
    return _outputProperties;
  }

  public Output getLastOutputElement() {
    return _lastOutputElement;
  }

  public void setMultiDocument(boolean flag) {
    _multiDocument = flag;
  }

  public boolean isMultiDocument() {
    return _multiDocument;
  }

  public void setCallsNodeset(boolean flag) {
    if (flag) {
      setMultiDocument(flag);
    }
    _callsNodeset = flag;
  }

  public boolean callsNodeset() {
    return _callsNodeset;
  }

  public void numberFormattingUsed() {
    _numberFormattingUsed = true;
        /*
         * Fix for bug 23046, if the stylesheet is included, set the
         * numberFormattingUsed flag to the parent stylesheet too.
         * AbstractTranslet.addDecimalFormat() will be inlined once for the
         * outer most stylesheet.
         */
    Stylesheet parent = getParentStylesheet();
    if (null != parent) {
      parent.numberFormattingUsed();
    }
  }

  public void setImportPrecedence(final int precedence) {
    // Set import precedence for this stylesheet
    _importPrecedence = precedence;

    // Set import precedence for all included stylesheets
    final Enumeration elements = elements();
    while (elements.hasMoreElements()) {
      SyntaxTreeNode child = (SyntaxTreeNode) elements.nextElement();
      if (child instanceof Include) {
        Stylesheet included = ((Include) child).getIncludedStylesheet();
        if (included != null && included._includedFrom == this) {
          included.setImportPrecedence(precedence);
        }
      }
    }

    // Set import precedence for the stylesheet that imported this one
    if (_importedFrom != null) {
      if (_importedFrom.getImportPrecedence() < precedence) {
        final Parser parser = getParser();
        final int nextPrecedence = parser.getNextImportPrecedence();
        _importedFrom.setImportPrecedence(nextPrecedence);
      }
    }
    // Set import precedence for the stylesheet that included this one
    else if (_includedFrom != null) {
      if (_includedFrom.getImportPrecedence() != precedence) {
        _includedFrom.setImportPrecedence(precedence);
      }
    }
  }

  public int getImportPrecedence() {
    return _importPrecedence;
  }

  /**
   * Get the minimum of the precedence of this stylesheet, any stylesheet
   * imported by this stylesheet and any include/import descendant of this
   * stylesheet.
   */
  public int getMinimumDescendantPrecedence() {
    if (_minimumDescendantPrecedence == -1) {
      // Start with precedence of current stylesheet as a basis.
      int min = getImportPrecedence();

      // Recursively examine all imported/included stylesheets.
      final int inclImpCount = (_includedStylesheets != null)
          ? _includedStylesheets.size()
          : 0;

      for (int i = 0; i < inclImpCount; i++) {
        int prec = ((Stylesheet) _includedStylesheets.elementAt(i))
            .getMinimumDescendantPrecedence();

        if (prec < min) {
          min = prec;
        }
      }

      _minimumDescendantPrecedence = min;
    }
    return _minimumDescendantPrecedence;
  }

  public boolean checkForLoop(String systemId) {
    // Return true if this stylesheet includes/imports itself
    if (_systemId != null && _systemId.equals(systemId)) {
      return true;
    }
    // Then check with any stylesheets that included/imported this one
    if (_parentStylesheet != null) {
      return _parentStylesheet.checkForLoop(systemId);
    }
    // Otherwise OK
    return false;
  }

  public void setParser(Parser parser) {
    super.setParser(parser);
    _name = makeStylesheetName("__stylesheet_");
  }

  public void setParentStylesheet(Stylesheet parent) {
    _parentStylesheet = parent;
  }

  public Stylesheet getParentStylesheet() {
    return _parentStylesheet;
  }

  public void setImportingStylesheet(Stylesheet parent) {
    _importedFrom = parent;
    parent.addIncludedStylesheet(this);
  }

  public void setIncludingStylesheet(Stylesheet parent) {
    _includedFrom = parent;
    parent.addIncludedStylesheet(this);
  }

  public void addIncludedStylesheet(Stylesheet child) {
    if (_includedStylesheets == null) {
      _includedStylesheets = new Vector();
    }
    _includedStylesheets.addElement(child);
  }

  public void setSystemId(String systemId) {
    if (systemId != null) {
      _systemId = SystemIDResolver.getAbsoluteURI(systemId);
    }
  }

  public String getSystemId() {
    return _systemId;
  }

  public void setSourceLoader(SourceLoader loader) {
    _loader = loader;
  }

  public SourceLoader getSourceLoader() {
    return _loader;
  }

  private QName makeStylesheetName(String prefix) {
    return getParser().getQName(prefix + getXSLTC().nextStylesheetSerial());
  }

  /**
   * Returns true if this stylesheet has global vars or params.
   */
  public boolean hasGlobals() {
    return _globals.size() > 0;
  }

  /**
   * Returns true if at least one template in the stylesheet has params
   * defined. Uses the variable <code>_hasLocalParams</code> to cache the
   * result.
   */
  public boolean hasLocalParams() {
    if (_hasLocalParams == null) {
      Vector templates = getAllValidTemplates();
      final int n = templates.size();
      for (int i = 0; i < n; i++) {
        final Template template = (Template) templates.elementAt(i);
        if (template.hasParams()) {
          _hasLocalParams = Boolean.TRUE;
          return true;
        }
      }
      _hasLocalParams = Boolean.FALSE;
      return false;
    } else {
      return _hasLocalParams.booleanValue();
    }
  }

  /**
   * Adds a single prefix mapping to this syntax tree node.
   *
   * @param prefix Namespace prefix.
   * @param uri Namespace URI.
   */
  protected void addPrefixMapping(String prefix, String uri) {
    if (prefix.equals(EMPTYSTRING) && uri.equals(XHTML_URI)) {
      return;
    }
    super.addPrefixMapping(prefix, uri);
  }

  /**
   * Store extension URIs
   */
  private void extensionURI(String prefixes, SymbolTable stable) {
    if (prefixes != null) {
      StringTokenizer tokens = new StringTokenizer(prefixes);
      while (tokens.hasMoreTokens()) {
        final String prefix = tokens.nextToken();
        final String uri = lookupNamespace(prefix);
        if (uri != null) {
          _extensions.put(uri, prefix);
        }
      }
    }
  }

  public boolean isExtension(String uri) {
    return (_extensions.get(uri) != null);
  }

  public void declareExtensionPrefixes(Parser parser) {
    final SymbolTable stable = parser.getSymbolTable();
    final String extensionPrefixes = getAttribute("extension-element-prefixes");
    extensionURI(extensionPrefixes, stable);
  }

  /**
   * Parse the version and uri fields of the stylesheet and add an
   * entry to the symbol table mapping the name <tt>__stylesheet_</tt>
   * to an instance of this class.
   */
  public void parseContents(Parser parser) {
    final SymbolTable stable = parser.getSymbolTable();

        /*
        // Make sure the XSL version set in this stylesheet
        if ((_version == null) || (_version.equals(EMPTYSTRING))) {
            reportError(this, parser, ErrorMsg.REQUIRED_ATTR_ERR,"version");
        }
        // Verify that the version is 1.0 and nothing else
        else if (!_version.equals("1.0")) {
            reportError(this, parser, ErrorMsg.XSL_VERSION_ERR, _version);
        }
        */

    // Add the implicit mapping of 'xml' to the XML namespace URI
    addPrefixMapping("xml", "http://www.w3.org/XML/1998/namespace");

    // Report and error if more than one stylesheet defined
    final Stylesheet sheet = stable.addStylesheet(_name, this);
    if (sheet != null) {
      // Error: more that one stylesheet defined
      ErrorMsg err = new ErrorMsg(ErrorMsg.MULTIPLE_STYLESHEET_ERR, this);
      parser.reportError(Constants.ERROR, err);
    }

    // If this is a simplified stylesheet we must create a template that
    // grabs the root node of the input doc ( <xsl:template match="/"/> ).
    // This template needs the current element (the one passed to this
    // method) as its only child, so the Template class has a special
    // method that handles this (parseSimplified()).
    if (_simplified) {
      stable.excludeURI(XSLT_URI);
      Template template = new Template();
      template.parseSimplified(this, parser);
    }
    // Parse the children of this node
    else {
      parseOwnChildren(parser);
    }
  }

  /**
   * Parse all direct children of the <xsl:stylesheet/> element.
   */
  public final void parseOwnChildren(Parser parser) {
    final SymbolTable stable = parser.getSymbolTable();
    final String excludePrefixes = getAttribute("exclude-result-prefixes");
    final String extensionPrefixes = getAttribute("extension-element-prefixes");

    // Exclude XSLT uri
    stable.pushExcludedNamespacesContext();
    stable.excludeURI(Constants.XSLT_URI);
    stable.excludeNamespaces(excludePrefixes);
    stable.excludeNamespaces(extensionPrefixes);

    final Vector contents = getContents();
    final int count = contents.size();

    // We have to scan the stylesheet element's top-level elements for
    // variables and/or parameters before we parse the other elements
    for (int i = 0; i < count; i++) {
      SyntaxTreeNode child = (SyntaxTreeNode) contents.elementAt(i);
      if ((child instanceof VariableBase) ||
          (child instanceof NamespaceAlias)) {
        parser.getSymbolTable().setCurrentNode(child);
        child.parseContents(parser);
      }
    }

    // Now go through all the other top-level elements...
    for (int i = 0; i < count; i++) {
      SyntaxTreeNode child = (SyntaxTreeNode) contents.elementAt(i);
      if (!(child instanceof VariableBase) &&
          !(child instanceof NamespaceAlias)) {
        parser.getSymbolTable().setCurrentNode(child);
        child.parseContents(parser);
      }

      // All template code should be compiled as methods if the
      // <xsl:apply-imports/> element was ever used in this stylesheet
      if (!_templateInlining && (child instanceof Template)) {
        Template template = (Template) child;
        String name = "template$dot$" + template.getPosition();
        template.setName(parser.getQName(name));
      }
    }

    stable.popExcludedNamespacesContext();
  }

  public void processModes() {
    if (_defaultMode == null) {
      _defaultMode = new Mode(null, this, Constants.EMPTYSTRING);
    }
    _defaultMode.processPatterns(_keys);
    final Enumeration modes = _modes.elements();
    while (modes.hasMoreElements()) {
      final Mode mode = (Mode) modes.nextElement();
      mode.processPatterns(_keys);
    }
  }

  private void compileModes(ClassGenerator classGen) {
    _defaultMode.compileApplyTemplates(classGen);
    final Enumeration modes = _modes.elements();
    while (modes.hasMoreElements()) {
      final Mode mode = (Mode) modes.nextElement();
      mode.compileApplyTemplates(classGen);
    }
  }

  public Mode getMode(QName modeName) {
    if (modeName == null) {
      if (_defaultMode == null) {
        _defaultMode = new Mode(null, this, Constants.EMPTYSTRING);
      }
      return _defaultMode;
    } else {
      Mode mode = (Mode) _modes.get(modeName);
      if (mode == null) {
        final String suffix = Integer.toString(_nextModeSerial++);
        _modes.put(modeName, mode = new Mode(modeName, this, suffix));
      }
      return mode;
    }
  }

  /**
   * Type check all the children of this node.
   */
  public Type typeCheck(SymbolTable stable) throws TypeCheckError {
    final int count = _globals.size();
    for (int i = 0; i < count; i++) {
      final VariableBase var = (VariableBase) _globals.elementAt(i);
      var.typeCheck(stable);
    }
    return typeCheckContents(stable);
  }

  /**
   * Translate the stylesheet into JVM bytecodes.
   */
  public void translate(ClassGenerator classGen, MethodGenerator methodGen) {
    translate();
  }

  private void addDOMField(ClassGenerator classGen) {
    final FieldGen fgen = new FieldGen(ACC_PUBLIC,
        Util.getJCRefType(DOM_INTF_SIG),
        DOM_FIELD,
        classGen.getConstantPool());
    classGen.addField(fgen.getField());
  }

  /**
   * Add a static field
   */
  private void addStaticField(ClassGenerator classGen, String type,
      String name) {
    final FieldGen fgen = new FieldGen(ACC_PROTECTED | ACC_STATIC,
        Util.getJCRefType(type),
        name,
        classGen.getConstantPool());
    classGen.addField(fgen.getField());

  }

  /**
   * Translate the stylesheet into JVM bytecodes.
   */
  public void translate() {
    _className = getXSLTC().getClassName();

    // Define a new class by extending TRANSLET_CLASS
    final ClassGenerator classGen =
        new ClassGenerator(_className,
            TRANSLET_CLASS,
            Constants.EMPTYSTRING,
            ACC_PUBLIC | ACC_SUPER,
            null, this);

    addDOMField(classGen);

    // Compile transform() to initialize parameters, globals & output
    // and run the transformation
    compileTransform(classGen);

    // Translate all non-template elements and filter out all templates
    final Enumeration elements = elements();
    while (elements.hasMoreElements()) {
      Object element = elements.nextElement();
      // xsl:template
      if (element instanceof Template) {
        // Separate templates by modes
        final Template template = (Template) element;
        //_templates.addElement(template);
        getMode(template.getModeName()).addTemplate(template);
      }
      // xsl:attribute-set
      else if (element instanceof AttributeSet) {
        ((AttributeSet) element).translate(classGen, null);
      } else if (element instanceof Output) {
        // save the element for later to pass to compileConstructor
        Output output = (Output) element;
        if (output.enabled()) {
          _lastOutputElement = output;
        }
      } else {
        // Global variables and parameters are handled elsewhere.
        // Other top-level non-template elements are ignored. Literal
        // elements outside of templates will never be output.
      }
    }

    checkOutputMethod();
    processModes();
    compileModes(classGen);
    compileStaticInitializer(classGen);
    compileConstructor(classGen, _lastOutputElement);

    if (!getParser().errorsFound()) {
      getXSLTC().dumpClass(classGen.getJavaClass());
    }
  }

  /**
   * Compile the namesArray, urisArray and typesArray into
   * the static initializer. They are read-only from the
   * translet. All translet instances can share a single
   * copy of this informtion.
   */
  private void compileStaticInitializer(ClassGenerator classGen) {
    final ConstantPoolGen cpg = classGen.getConstantPool();
    final InstructionList il = new InstructionList();

    final MethodGenerator staticConst =
        new MethodGenerator(ACC_PUBLIC | ACC_STATIC,
            com.sun.org.apache.bcel.internal.generic.Type.VOID,
            null, null, "<clinit>",
            _className, il, cpg);

    addStaticField(classGen, "[" + STRING_SIG, STATIC_NAMES_ARRAY_FIELD);
    addStaticField(classGen, "[" + STRING_SIG, STATIC_URIS_ARRAY_FIELD);
    addStaticField(classGen, "[I", STATIC_TYPES_ARRAY_FIELD);
    addStaticField(classGen, "[" + STRING_SIG, STATIC_NAMESPACE_ARRAY_FIELD);
    // Create fields of type char[] that will contain literal text from
    // the stylesheet.
    final int charDataFieldCount = getXSLTC().getCharacterDataCount();
    for (int i = 0; i < charDataFieldCount; i++) {
      addStaticField(classGen, STATIC_CHAR_DATA_FIELD_SIG,
          STATIC_CHAR_DATA_FIELD + i);
    }

    // Put the names array into the translet - used for dom/translet mapping
    final Vector namesIndex = getXSLTC().getNamesIndex();
    int size = namesIndex.size();
    String[] namesArray = new String[size];
    String[] urisArray = new String[size];
    int[] typesArray = new int[size];

    int index;
    for (int i = 0; i < size; i++) {
      String encodedName = (String) namesIndex.elementAt(i);
      if ((index = encodedName.lastIndexOf(':')) > -1) {
        urisArray[i] = encodedName.substring(0, index);
      }

      index = index + 1;
      if (encodedName.charAt(index) == '@') {
        typesArray[i] = DTM.ATTRIBUTE_NODE;
        index++;
      } else if (encodedName.charAt(index) == '?') {
        typesArray[i] = DTM.NAMESPACE_NODE;
        index++;
      } else {
        typesArray[i] = DTM.ELEMENT_NODE;
      }

      if (index == 0) {
        namesArray[i] = encodedName;
      } else {
        namesArray[i] = encodedName.substring(index);
      }
    }

    staticConst.markChunkStart();
    il.append(new PUSH(cpg, size));
    il.append(new ANEWARRAY(cpg.addClass(STRING)));
    int namesArrayRef = cpg.addFieldref(_className,
        STATIC_NAMES_ARRAY_FIELD,
        NAMES_INDEX_SIG);
    il.append(new PUTSTATIC(namesArrayRef));
    staticConst.markChunkEnd();

    for (int i = 0; i < size; i++) {
      final String name = namesArray[i];
      staticConst.markChunkStart();
      il.append(new GETSTATIC(namesArrayRef));
      il.append(new PUSH(cpg, i));
      il.append(new PUSH(cpg, name));
      il.append(AASTORE);
      staticConst.markChunkEnd();
    }

    staticConst.markChunkStart();
    il.append(new PUSH(cpg, size));
    il.append(new ANEWARRAY(cpg.addClass(STRING)));
    int urisArrayRef = cpg.addFieldref(_className,
        STATIC_URIS_ARRAY_FIELD,
        URIS_INDEX_SIG);
    il.append(new PUTSTATIC(urisArrayRef));
    staticConst.markChunkEnd();

    for (int i = 0; i < size; i++) {
      final String uri = urisArray[i];
      staticConst.markChunkStart();
      il.append(new GETSTATIC(urisArrayRef));
      il.append(new PUSH(cpg, i));
      il.append(new PUSH(cpg, uri));
      il.append(AASTORE);
      staticConst.markChunkEnd();
    }

    staticConst.markChunkStart();
    il.append(new PUSH(cpg, size));
    il.append(new NEWARRAY(BasicType.INT));
    int typesArrayRef = cpg.addFieldref(_className,
        STATIC_TYPES_ARRAY_FIELD,
        TYPES_INDEX_SIG);
    il.append(new PUTSTATIC(typesArrayRef));
    staticConst.markChunkEnd();

    for (int i = 0; i < size; i++) {
      final int nodeType = typesArray[i];
      staticConst.markChunkStart();
      il.append(new GETSTATIC(typesArrayRef));
      il.append(new PUSH(cpg, i));
      il.append(new PUSH(cpg, nodeType));
      il.append(IASTORE);
    }

    // Put the namespace names array into the translet
    final Vector namespaces = getXSLTC().getNamespaceIndex();
    staticConst.markChunkStart();
    il.append(new PUSH(cpg, namespaces.size()));
    il.append(new ANEWARRAY(cpg.addClass(STRING)));
    int namespaceArrayRef = cpg.addFieldref(_className,
        STATIC_NAMESPACE_ARRAY_FIELD,
        NAMESPACE_INDEX_SIG);
    il.append(new PUTSTATIC(namespaceArrayRef));
    staticConst.markChunkEnd();

    for (int i = 0; i < namespaces.size(); i++) {
      final String ns = (String) namespaces.elementAt(i);
      staticConst.markChunkStart();
      il.append(new GETSTATIC(namespaceArrayRef));
      il.append(new PUSH(cpg, i));
      il.append(new PUSH(cpg, ns));
      il.append(AASTORE);
      staticConst.markChunkEnd();
    }

    // Grab all the literal text in the stylesheet and put it in a char[]
    final int charDataCount = getXSLTC().getCharacterDataCount();
    final int toCharArray = cpg.addMethodref(STRING, "toCharArray", "()[C");
    for (int i = 0; i < charDataCount; i++) {
      staticConst.markChunkStart();
      il.append(new PUSH(cpg, getXSLTC().getCharacterData(i)));
      il.append(new INVOKEVIRTUAL(toCharArray));
      il.append(new PUTSTATIC(cpg.addFieldref(_className,
          STATIC_CHAR_DATA_FIELD + i,
          STATIC_CHAR_DATA_FIELD_SIG)));
      staticConst.markChunkEnd();
    }

    il.append(RETURN);

    classGen.addMethod(staticConst);

  }

  /**
   * Compile the translet's constructor
   */
  private void compileConstructor(ClassGenerator classGen, Output output) {

    final ConstantPoolGen cpg = classGen.getConstantPool();
    final InstructionList il = new InstructionList();

    final MethodGenerator constructor =
        new MethodGenerator(ACC_PUBLIC,
            com.sun.org.apache.bcel.internal.generic.Type.VOID,
            null, null, "<init>",
            _className, il, cpg);

    // Call the constructor in the AbstractTranslet superclass
    il.append(classGen.loadTranslet());
    il.append(new INVOKESPECIAL(cpg.addMethodref(TRANSLET_CLASS,
        "<init>", "()V")));

    constructor.markChunkStart();
    il.append(classGen.loadTranslet());
    il.append(new GETSTATIC(cpg.addFieldref(_className,
        STATIC_NAMES_ARRAY_FIELD,
        NAMES_INDEX_SIG)));
    il.append(new PUTFIELD(cpg.addFieldref(TRANSLET_CLASS,
        NAMES_INDEX,
        NAMES_INDEX_SIG)));

    il.append(classGen.loadTranslet());
    il.append(new GETSTATIC(cpg.addFieldref(_className,
        STATIC_URIS_ARRAY_FIELD,
        URIS_INDEX_SIG)));
    il.append(new PUTFIELD(cpg.addFieldref(TRANSLET_CLASS,
        URIS_INDEX,
        URIS_INDEX_SIG)));
    constructor.markChunkEnd();

    constructor.markChunkStart();
    il.append(classGen.loadTranslet());
    il.append(new GETSTATIC(cpg.addFieldref(_className,
        STATIC_TYPES_ARRAY_FIELD,
        TYPES_INDEX_SIG)));
    il.append(new PUTFIELD(cpg.addFieldref(TRANSLET_CLASS,
        TYPES_INDEX,
        TYPES_INDEX_SIG)));
    constructor.markChunkEnd();

    constructor.markChunkStart();
    il.append(classGen.loadTranslet());
    il.append(new GETSTATIC(cpg.addFieldref(_className,
        STATIC_NAMESPACE_ARRAY_FIELD,
        NAMESPACE_INDEX_SIG)));
    il.append(new PUTFIELD(cpg.addFieldref(TRANSLET_CLASS,
        NAMESPACE_INDEX,
        NAMESPACE_INDEX_SIG)));
    constructor.markChunkEnd();

    constructor.markChunkStart();
    il.append(classGen.loadTranslet());
    il.append(new PUSH(cpg, AbstractTranslet.CURRENT_TRANSLET_VERSION));
    il.append(new PUTFIELD(cpg.addFieldref(TRANSLET_CLASS,
        TRANSLET_VERSION_INDEX,
        TRANSLET_VERSION_INDEX_SIG)));
    constructor.markChunkEnd();

    if (_hasIdCall) {
      constructor.markChunkStart();
      il.append(classGen.loadTranslet());
      il.append(new PUSH(cpg, Boolean.TRUE));
      il.append(new PUTFIELD(cpg.addFieldref(TRANSLET_CLASS,
          HASIDCALL_INDEX,
          HASIDCALL_INDEX_SIG)));
      constructor.markChunkEnd();
    }

    // Compile in code to set the output configuration from <xsl:output>
    if (output != null) {
      // Set all the output settings files in the translet
      constructor.markChunkStart();
      output.translate(classGen, constructor);
      constructor.markChunkEnd();
    }

    // Compile default decimal formatting symbols.
    // This is an implicit, nameless xsl:decimal-format top-level element.
    if (_numberFormattingUsed) {
      constructor.markChunkStart();
      DecimalFormatting.translateDefaultDFS(classGen, constructor);
      constructor.markChunkEnd();
    }

    il.append(RETURN);

    classGen.addMethod(constructor);
  }

  /**
   * Compile a topLevel() method into the output class. This method is
   * called from transform() to handle all non-template top-level elements.
   * Returns the signature of the topLevel() method.
   *
   * Global variables/params and keys are first sorted to resolve
   * dependencies between them. The XSLT 1.0 spec does not allow a key
   * to depend on a variable. However, for compatibility with Xalan
   * interpretive, that type of dependency is allowed. Note also that
   * the buildKeys() method is still generated as it is used by the
   * LoadDocument class, but it no longer called from transform().
   */
  private String compileTopLevel(ClassGenerator classGen) {

    final ConstantPoolGen cpg = classGen.getConstantPool();

    final com.sun.org.apache.bcel.internal.generic.Type[] argTypes = {
        Util.getJCRefType(DOM_INTF_SIG),
        Util.getJCRefType(NODE_ITERATOR_SIG),
        Util.getJCRefType(TRANSLET_OUTPUT_SIG)
    };

    final String[] argNames = {
        DOCUMENT_PNAME, ITERATOR_PNAME, TRANSLET_OUTPUT_PNAME
    };

    final InstructionList il = new InstructionList();

    final MethodGenerator toplevel =
        new MethodGenerator(ACC_PUBLIC,
            com.sun.org.apache.bcel.internal.generic.Type.VOID,
            argTypes, argNames,
            "topLevel", _className, il,
            classGen.getConstantPool());

    toplevel.addException("com.sun.org.apache.xalan.internal.xsltc.TransletException");

    // Define and initialize 'current' variable with the root node
    final LocalVariableGen current =
        toplevel.addLocalVariable("current",
            com.sun.org.apache.bcel.internal.generic.Type.INT,
            null, null);

    final int setFilter = cpg.addInterfaceMethodref(DOM_INTF,
        "setFilter",
        "(Lcom/sun/org/apache/xalan/internal/xsltc/StripFilter;)V");

    final int gitr = cpg.addInterfaceMethodref(DOM_INTF,
        "getIterator",
        "()" + NODE_ITERATOR_SIG);
    il.append(toplevel.loadDOM());
    il.append(new INVOKEINTERFACE(gitr, 1));
    il.append(toplevel.nextNode());
    current.setStart(il.append(new ISTORE(current.getIndex())));

    // Create a new list containing variables/params + keys
    Vector varDepElements = new Vector(_globals);
    Enumeration elements = elements();
    while (elements.hasMoreElements()) {
      final Object element = elements.nextElement();
      if (element instanceof Key) {
        varDepElements.add(element);
      }
    }

    // Determine a partial order for the variables/params and keys
    varDepElements = resolveDependencies(varDepElements);

    // Translate vars/params and keys in the right order
    final int count = varDepElements.size();
    for (int i = 0; i < count; i++) {
      final TopLevelElement tle = (TopLevelElement) varDepElements.elementAt(i);
      tle.translate(classGen, toplevel);
      if (tle instanceof Key) {
        final Key key = (Key) tle;
        _keys.put(key.getName(), key);
      }
    }

    // Compile code for other top-level elements
    Vector whitespaceRules = new Vector();
    elements = elements();
    while (elements.hasMoreElements()) {
      final Object element = elements.nextElement();
      // xsl:decimal-format
      if (element instanceof DecimalFormatting) {
        ((DecimalFormatting) element).translate(classGen, toplevel);
      }
      // xsl:strip/preserve-space
      else if (element instanceof Whitespace) {
        whitespaceRules.addAll(((Whitespace) element).getRules());
      }
    }

    // Translate all whitespace strip/preserve rules
    if (whitespaceRules.size() > 0) {
      Whitespace.translateRules(whitespaceRules, classGen);
    }

    if (classGen.containsMethod(STRIP_SPACE, STRIP_SPACE_PARAMS) != null) {
      il.append(toplevel.loadDOM());
      il.append(classGen.loadTranslet());
      il.append(new INVOKEINTERFACE(setFilter, 2));
    }

    il.append(RETURN);

    // Compute max locals + stack and add method to class
    classGen.addMethod(toplevel);

    return ("(" + DOM_INTF_SIG + NODE_ITERATOR_SIG + TRANSLET_OUTPUT_SIG + ")V");
  }

  /**
   * This method returns a vector with variables/params and keys in the
   * order in which they are to be compiled for initialization. The order
   * is determined by analyzing the dependencies between them. The XSLT 1.0
   * spec does not allow a key to depend on a variable. However, for
   * compatibility with Xalan interpretive, that type of dependency is
   * allowed and, therefore, consider to determine the partial order.
   */
  private Vector resolveDependencies(Vector input) {
        /* DEBUG CODE - INGORE
        for (int i = 0; i < input.size(); i++) {
            final TopLevelElement e = (TopLevelElement) input.elementAt(i);
            System.out.println("e = " + e + " depends on:");
            Vector dep = e.getDependencies();
            for (int j = 0; j < (dep != null ? dep.size() : 0); j++) {
                System.out.println("\t" + dep.elementAt(j));
            }
        }
        System.out.println("=================================");
        */

    Vector result = new Vector();
    while (input.size() > 0) {
      boolean changed = false;
      for (int i = 0; i < input.size(); ) {
        final TopLevelElement vde = (TopLevelElement) input.elementAt(i);
        final Vector dep = vde.getDependencies();
        if (dep == null || result.containsAll(dep)) {
          result.addElement(vde);
          input.remove(i);
          changed = true;
        } else {
          i++;
        }
      }

      // If nothing was changed in this pass then we have a circular ref
      if (!changed) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.CIRCULAR_VARIABLE_ERR,
            input.toString(), this);
        getParser().reportError(Constants.ERROR, err);
        return (result);
      }
    }

        /* DEBUG CODE - INGORE
        System.out.println("=================================");
        for (int i = 0; i < result.size(); i++) {
            final TopLevelElement e = (TopLevelElement) result.elementAt(i);
            System.out.println("e = " + e);
        }
        */

    return result;
  }

  /**
   * Compile a buildKeys() method into the output class. Note that keys
   * for the input document are created in topLevel(), not in this method.
   * However, we still need this method to create keys for documents loaded
   * via the XPath document() function.
   */
  private String compileBuildKeys(ClassGenerator classGen) {
    final ConstantPoolGen cpg = classGen.getConstantPool();

    final com.sun.org.apache.bcel.internal.generic.Type[] argTypes = {
        Util.getJCRefType(DOM_INTF_SIG),
        Util.getJCRefType(NODE_ITERATOR_SIG),
        Util.getJCRefType(TRANSLET_OUTPUT_SIG),
        com.sun.org.apache.bcel.internal.generic.Type.INT
    };

    final String[] argNames = {
        DOCUMENT_PNAME, ITERATOR_PNAME, TRANSLET_OUTPUT_PNAME, "current"
    };

    final InstructionList il = new InstructionList();

    final MethodGenerator buildKeys =
        new MethodGenerator(ACC_PUBLIC,
            com.sun.org.apache.bcel.internal.generic.Type.VOID,
            argTypes, argNames,
            "buildKeys", _className, il,
            classGen.getConstantPool());

    buildKeys.addException("com.sun.org.apache.xalan.internal.xsltc.TransletException");

    final Enumeration elements = elements();
    while (elements.hasMoreElements()) {
      // xsl:key
      final Object element = elements.nextElement();
      if (element instanceof Key) {
        final Key key = (Key) element;
        key.translate(classGen, buildKeys);
        _keys.put(key.getName(), key);
      }
    }

    il.append(RETURN);

    // Compute max locals + stack and add method to class
    buildKeys.stripAttributes(true);
    buildKeys.setMaxLocals();
    buildKeys.setMaxStack();
    buildKeys.removeNOPs();

    classGen.addMethod(buildKeys.getMethod());

    return ("(" + DOM_INTF_SIG + NODE_ITERATOR_SIG + TRANSLET_OUTPUT_SIG + "I)V");
  }

  /**
   * Compile transform() into the output class. This method is used to
   * initialize global variables and global parameters. The current node
   * is set to be the document's root node.
   */
  private void compileTransform(ClassGenerator classGen) {
    final ConstantPoolGen cpg = classGen.getConstantPool();

        /*
         * Define the the method transform with the following signature:
         * void transform(DOM, NodeIterator, HandlerBase)
         */
    final com.sun.org.apache.bcel.internal.generic.Type[] argTypes =
        new com.sun.org.apache.bcel.internal.generic.Type[3];
    argTypes[0] = Util.getJCRefType(DOM_INTF_SIG);
    argTypes[1] = Util.getJCRefType(NODE_ITERATOR_SIG);
    argTypes[2] = Util.getJCRefType(TRANSLET_OUTPUT_SIG);

    final String[] argNames = new String[3];
    argNames[0] = DOCUMENT_PNAME;
    argNames[1] = ITERATOR_PNAME;
    argNames[2] = TRANSLET_OUTPUT_PNAME;

    final InstructionList il = new InstructionList();
    final MethodGenerator transf =
        new MethodGenerator(ACC_PUBLIC,
            com.sun.org.apache.bcel.internal.generic.Type.VOID,
            argTypes, argNames,
            "transform",
            _className,
            il,
            classGen.getConstantPool());
    transf.addException("com.sun.org.apache.xalan.internal.xsltc.TransletException");

    // Define and initialize current with the root node
    final LocalVariableGen current =
        transf.addLocalVariable("current",
            com.sun.org.apache.bcel.internal.generic.Type.INT,
            null, null);
    final String applyTemplatesSig = classGen.getApplyTemplatesSig();
    final int applyTemplates = cpg.addMethodref(getClassName(),
        "applyTemplates",
        applyTemplatesSig);
    final int domField = cpg.addFieldref(getClassName(),
        DOM_FIELD,
        DOM_INTF_SIG);

    // push translet for PUTFIELD
    il.append(classGen.loadTranslet());
    // prepare appropriate DOM implementation

    if (isMultiDocument()) {
      il.append(new NEW(cpg.addClass(MULTI_DOM_CLASS)));
      il.append(DUP);
    }

    il.append(classGen.loadTranslet());
    il.append(transf.loadDOM());
    il.append(new INVOKEVIRTUAL(cpg.addMethodref(TRANSLET_CLASS,
        "makeDOMAdapter",
        "(" + DOM_INTF_SIG + ")" +
            DOM_ADAPTER_SIG)));
    // DOMAdapter is on the stack

    if (isMultiDocument()) {
      final int init = cpg.addMethodref(MULTI_DOM_CLASS,
          "<init>",
          "(" + DOM_INTF_SIG + ")V");
      il.append(new INVOKESPECIAL(init));
      // MultiDOM is on the stack
    }

    //store to _dom variable
    il.append(new PUTFIELD(domField));

    // continue with globals initialization
    final int gitr = cpg.addInterfaceMethodref(DOM_INTF,
        "getIterator",
        "()" + NODE_ITERATOR_SIG);
    il.append(transf.loadDOM());
    il.append(new INVOKEINTERFACE(gitr, 1));
    il.append(transf.nextNode());
    current.setStart(il.append(new ISTORE(current.getIndex())));

    // Transfer the output settings to the output post-processor
    il.append(classGen.loadTranslet());
    il.append(transf.loadHandler());
    final int index = cpg.addMethodref(TRANSLET_CLASS,
        "transferOutputSettings",
        "(" + OUTPUT_HANDLER_SIG + ")V");
    il.append(new INVOKEVIRTUAL(index));

        /*
         * Compile buildKeys() method. Note that this method is not
         * invoked here as keys for the input document are now created
         * in topLevel(). However, this method is still needed by the
         * LoadDocument class.
         */
    final String keySig = compileBuildKeys(classGen);
    final int keyIdx = cpg.addMethodref(getClassName(),
        "buildKeys", keySig);

    // Look for top-level elements that need handling
    final Enumeration toplevel = elements();
    if (_globals.size() > 0 || toplevel.hasMoreElements()) {
      // Compile method for handling top-level elements
      final String topLevelSig = compileTopLevel(classGen);
      // Get a reference to that method
      final int topLevelIdx = cpg.addMethodref(getClassName(),
          "topLevel",
          topLevelSig);
      // Push all parameters on the stack and call topLevel()
      il.append(classGen.loadTranslet()); // The 'this' pointer
      il.append(classGen.loadTranslet());
      il.append(new GETFIELD(domField));  // The DOM reference
      il.append(transf.loadIterator());
      il.append(transf.loadHandler());    // The output handler
      il.append(new INVOKEVIRTUAL(topLevelIdx));
    }

    // start document
    il.append(transf.loadHandler());
    il.append(transf.startDocument());

    // push first arg for applyTemplates
    il.append(classGen.loadTranslet());
    // push translet for GETFIELD to get DOM arg
    il.append(classGen.loadTranslet());
    il.append(new GETFIELD(domField));
    // push remaining 2 args
    il.append(transf.loadIterator());
    il.append(transf.loadHandler());
    il.append(new INVOKEVIRTUAL(applyTemplates));
    // endDocument
    il.append(transf.loadHandler());
    il.append(transf.endDocument());

    il.append(RETURN);

    // Compute max locals + stack and add method to class
    classGen.addMethod(transf);

  }

  /**
   * Peephole optimization: Remove sequences of [ALOAD, POP].
   */
  private void peepHoleOptimization(MethodGenerator methodGen) {
    final String pattern = "`aload'`pop'`instruction'";
    final InstructionList il = methodGen.getInstructionList();
    final InstructionFinder find = new InstructionFinder(il);
    for (Iterator iter = find.search(pattern); iter.hasNext(); ) {
      InstructionHandle[] match = (InstructionHandle[]) iter.next();
      try {
        il.delete(match[0], match[1]);
      } catch (TargetLostException e) {
        // TODO: move target down into the list
      }
    }
  }

  public int addParam(Param param) {
    _globals.addElement(param);
    return _globals.size() - 1;
  }

  public int addVariable(Variable global) {
    _globals.addElement(global);
    return _globals.size() - 1;
  }

  public void display(int indent) {
    indent(indent);
    Util.println("Stylesheet");
    displayContents(indent + IndentIncrement);
  }

  // do we need this wrapper ?????
  public String getNamespace(String prefix) {
    return lookupNamespace(prefix);
  }

  public String getClassName() {
    return _className;
  }

  public Vector getTemplates() {
    return _templates;
  }

  public Vector getAllValidTemplates() {
    // Return templates if no imported/included stylesheets
    if (_includedStylesheets == null) {
      return _templates;
    }

    // Is returned value cached?
    if (_allValidTemplates == null) {
      Vector templates = new Vector();
      templates.addAll(_templates);
      int size = _includedStylesheets.size();
      for (int i = 0; i < size; i++) {
        Stylesheet included = (Stylesheet) _includedStylesheets.elementAt(i);
        templates.addAll(included.getAllValidTemplates());
      }
      //templates.addAll(_templates);

      // Cache results in top-level stylesheet only
      if (_parentStylesheet != null) {
        return templates;
      }
      _allValidTemplates = templates;
    }

    return _allValidTemplates;
  }

  protected void addTemplate(Template template) {
    _templates.addElement(template);
  }
}
